using System;
using System.Collections.Generic;
using NCommet.Core.Services;
using NCommet.Core.Agents;
using NCommet.Core.Exceptions;

namespace NCommet.Core
{
    /// <summary>
    /// ChildrenCollection holds the children items of an item.
    /// </summary>
    public class ChildrenCollection : IList<Item>
    {
        internal ChildrenCollection(Item owner)
        {
            this.owner = owner;
        }

        /// <summary>
        /// Removes a child from this collection, optionally deleting it completely.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        /// <param name="deleteChild">If true, the child is deleted; if false, it becomes orphan.</param>
        /// <returns>True, if the item exists in the collection and is removed successfully.</returns>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists and the item cannot be removed from the collection.
        /// </exception>
        internal bool Remove(Item item, bool deleteChild)
        {
            int idx = store.IndexOf(item);
            if (idx < 0)
                return false;

            Item toDelete = store[idx];

            AssertCanRemove(toDelete);

            // raise event if we're going to kill it
            if (deleteChild)
            {
                ItemCancellableEventArgs e = new ItemCancellableEventArgs(toDelete);
                EventSink.Instance.FireItemDeleting(e);
                if (e.Cancel)
                    return false;
            }

            toDelete.Parent = null;
            store.RemoveAt(idx);
            FixIndices(idx);
            if (deleteChild)
                toDelete.PerformDelete();
            return true;
        }

        /// <summary>
        /// Removes an item from the collection without deleting it.
        /// The item's parent is set to null.
        /// </summary>
        /// <param name="item">The item to detach.</param>
        /// <returns>True, if the item exists in the collection and is removed successfully.</returns>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists and the item cannot be removed from the collection.
        /// </exception>
        public bool Detach(Item item)
        {
            return Remove(item, false);
        }

        /// <summary>
        /// Fixes the Order property of all items in this collection, starting at the
        /// designated index, inclusive.
        /// </summary>
        /// <param name="startIndex">The list index to start fixing indices from.</param>
        private void FixIndices(int startIndex)
        {
            for (int i = startIndex; i < Count; i++)
                this[i].Order = i;
        }

        private Item owner;

        /// <summary>
        /// Returns the parent of the children collection.
        /// </summary>
        public Item Owner { get { return owner; } }

        /// <summary>
        /// Returns the backing list that is stored in the parent.
        /// </summary>
        private IList<Item> store { get { return owner.internalChildren; } }

        /// <summary>
        /// Swaps two child items in the collection. The items are specified by their indices.
        /// </summary>        
        /// <param name="idxA">The index of the first item in the collection.</param>
        /// <param name="idxB">The index of the second item in the collection.</param>        
        public void SwapItems(int idxA, int idxB)
        {
            if (idxA == idxB)
                return;

            Item a = store[idxA];
            store[idxA] = store[idxB];
            store[idxB] = a;
            store[idxA].Order = idxA;
            store[idxB].Order = idxB;

            EventSink.Instance.FireChildrenReordered(new ItemEventArgs(this.owner));
        }

        /// <summary>
        /// Moves the item at position <paramref name="currentIndex"/> to the position <paramref name="newIndex"/>.
        /// </summary>        
        /// <param name="currentIndex">The current position of the item.</param>
        /// <param name="newIndex">The position where the item is moved.</param>
        public void MoveAt(int currentIndex, int newIndex)
        {
            Item toMove = store[currentIndex];
            if (newIndex < currentIndex)
            {
                for (int i = currentIndex; i > newIndex; i--)
                {
                    store[i] = store[i - 1];
                    store[i].Order = i;
                }
            }
            else if (newIndex > currentIndex)
            {
                for (int i = currentIndex; i < newIndex; i++)
                {
                    store[i] = store[i + 1];
                    store[i].Order = i;
                }
            }
            store[newIndex] = toMove;
            store[newIndex].Order = newIndex;
            EventSink.Instance.FireChildrenReordered(new ItemEventArgs(this.owner));
        }

        /// <summary>
        /// Returns an array containing the ids of the children.
        /// </summary>
        /// <returns>An array with the children ids.</returns>
        public int[] GetChildrenIds()
        {
            int[] result = new int[Count];
            for (int i = 0; i < Count; i++)
                result[i] = this[i].Id;
            return result;
        }

        /// <summary>
        /// Filters children based on their ItemType.
        /// </summary>
        /// <param name="ItemType">The type of the item.</param>
        /// <returns>A list of <see cref="NCommet.Core.Item">Items</see>.</returns>
        public IList<Item> Filter(string ItemType)
        {
            List<Item> results = new List<Item>();
            foreach (Item item in this.store)
                if (item.ItemType == ItemType)
                    results.Add(item);

            return results;
        }

        #region AssertCan Helper Methods
        private void AssertCanAttach(Item item)
        {
            if (this.owner.TypedTreeValidationDisabled)
                return;
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            ICollection<string> errors;
            if (ttvalidator != null && !ttvalidator.CanAttach(this.owner, item, out errors))
                throw new ValidationException(errors);
        }

        private void AssertCanRemove(Item child)
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            ICollection<string> errors;
            if (ttvalidator != null && !ttvalidator.CanRemove(owner, child, out errors))
                throw new ValidationException(errors);
        }
        #endregion

        #region IList<Item> Members

        /// <summary>
        /// Returns the index of the specified item in the children collection.
        /// </summary>
        /// <param name="item">The item in the children collection.</param>
        /// <returns>The index of the item.</returns>
        public int IndexOf(Item item)
        {
            return store.IndexOf(item);
        }

        /// <summary>
        /// Inserts an item to the children collection at the specified index.
        /// If an authorization manager is set, AddChild access level must be set on the parent item.
        /// If a typed tree validator is set, the candidate child must be able to be attached on the parent.
        /// </summary>        
        /// <param name="index">The index in the children collection, where this item is inserted.</param>
        /// <param name="item">The item to be inserted in the children collection.</param>
        /// <exception cref="CoreException">
        /// If <paramref name="Item"/> has already a parent.
        /// </exception>
        /// <exception cref="AuthException">
        /// If an authorization manager exists and the logged-in user does not have <see cref="NCommet.Core.Agents.AccessLevel.AddChild"/> access on 
        /// the future parent of this item.
        /// </exception>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists and the item cannot be attached to this collection.
        /// </exception>
        public void Insert(int index, Item item)
        {
            if (item.Parent != null)
                throw new CoreException("Item already has parent");

            owner.AssertAuth(AccessLevel.AddChild);

            AssertCanAttach(item);

            item.Parent = this.owner;

            store.Insert(index, item);
            FixIndices(index);

            EventSink.Instance.FireItemAdded(new ItemEventArgs(item));
        }

        /// <summary>
        /// Removes the item at the specified index and deletes it.
        /// The cancellable <see cref="NCommet.Core.Services.EventSink.ItemDeleting"/> is fired prior to item deletion.
        /// If a typed tree validator is set, it's checked that the item can be removed.
        /// </summary>
        /// <param name="index">The index of item that is going to be removed from the collection.</param>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists and the item cannot be removed from the collection.
        /// </exception>
        public void RemoveAt(int index)
        {
            Item toDelete = store[index];
            AssertCanRemove(toDelete);

            ItemCancellableEventArgs e = new ItemCancellableEventArgs(toDelete);
            EventSink.Instance.FireItemDeleting(e);
            if (e.Cancel)
                return;

            store.RemoveAt(index);
            FixIndices(index);
            toDelete.PerformDelete();
        }

        /// <summary>
        /// Returns the item at the specified index in the collection.
        /// </summary>
        /// <remarks>
        /// Setting is not allowed, the setter property throws <see cref="NotSupportedException" />.
        /// </remarks>
        /// <param name="index">The index of the item.</param>
        /// <returns>The item at the specified index.</returns>
        /// <exception cref="NotSupportedException">
        /// If a an attempt to set the item at a specified index is made.
        /// </exception>
        public Item this[int index]
        {
            get
            {
                return store[index];
            }
            set
            {
                throw new NotSupportedException("Set is not allowed");
            }
        }

        #endregion

        #region ICollection<Item> Members

        /// <summary>
        /// Adds the specified item to the children collection.
        /// </summary>
        /// <param name="item">The item to be added in the children collection.</param>
        /// <exception cref="CoreException">
        /// <ul>
        /// <li>If the item has already a parent.</li>
        /// <li>If the candidate child is an ancestor of the parent.</li>
        /// </ul>
        /// </exception>
        /// <exception cref="AuthException">
        /// If an authorization manager exists and the logged-in user does not have <see cref="NCommet.Core.Agents.AccessLevel.AddChild"/> access on 
        /// the future parent of this item.
        /// </exception>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists and the candidate child cannot be attached to the parent.
        /// </exception>
        public void Add(Item item)
        {
            if (item.Parent != null && item.Parent != this.owner)
                throw new Exceptions.CoreException("Item already has parent, use Move instead. OldParentId=" + item.Parent.Id + ", ThisParentId=" + owner.Id);
            if (item.IsParentOf(this.owner))
                throw new Exceptions.CoreException("Cannot add item to its descendant.");
            owner.AssertAuth(AccessLevel.AddChild);
            AssertCanAttach(item);

            //Set the child Item's Parent
            item.Parent = this.owner;

            //Add it to the collection
            this.store.Add(item);

            //Fix the child Item's order
            item.Order = this.store.Count - 1;

            //Throw a message in a bottle			
            EventSink.Instance.FireItemAdded(new ItemEventArgs(item));
        }

        /// <summary>
        /// Removes and deletes all children items. Typed tree validation is not enforced
        /// even if a typed tree validator is set. The operation is performed in one transaction
        /// so if one item deletion fails, no data is lost. 
        /// </summary>
        public void Clear()
        {
            // TODO: Typed Tree Validation???
            IPersister persister = NCommetContainer.Instance.Persister;
            persister.BeginTransaction();
            try
            {
                List<Item> tmp = new List<Item>(store);
                foreach (Item it in tmp)
                    this.Remove(it, true);
                persister.CommitTransaction();

            }
            catch (Exception ex)
            {
                persister.RollbackTransaction();
                throw new Exception("error in transaction", ex);
            }
            EventSink.Instance.FireChildrenCleared(new ItemEventArgs(this.owner));
        }

        /// <summary>
        /// Determines whether the collection contains the specified item.
        /// </summary>
        /// <param name="item">The item that is checked.</param>
        /// <returns>True, if the item exists in the children collection.</returns>
        public bool Contains(Item item)
        {
            return store.Contains(item);
        }

        /// <summary>
        /// Copies the children items to the specified array, starting at the given index.
        /// </summary>
        /// <param name="array">The array where the children are copied.</param>
        /// <param name="arrayIndex">The starting index.</param>
        public void CopyTo(Item[] array, int arrayIndex)
        {
            store.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Gets the number of elements contained in the collection.
        /// </summary>
        public int Count
        {
            get { return store.Count; }
        }

        /// <summary>
        /// Specifies whether this collection is readonly or not. The <c>ChildrenCollection</c> is never readonly.
        /// </summary>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Removes the specified item from the collection and deletes it.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        /// <returns>True, if the item exists in the collection and is removed successfully.</returns>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists and the item cannot be removed from the collection.
        /// </exception>
        public bool Remove(Item item)
        {
            return Remove(item, true);
        }

        #endregion

        #region IEnumerable<Item> Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>An enumerator of <see cref="NCommet.Core.Item">Items</see></returns>
        public IEnumerator<Item> GetEnumerator()
        {
            return store.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>An enumerator to iterate through the collection.</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return store.GetEnumerator();
        }

        #endregion

    }
}
